
#include <BEntry>
#include <BFile>
#include <BVBoxLayout>
#include <BHBoxLayout>
#include <BRadioBox>
#include <BLabel>
#include <BPainter>
#include <BSpring>
#include <BScript>
#include <BTabWidget>
#include <BApplication>

#include "SearchDialog.h"

SearchDialog::SearchDialog(BWidget* parent) : BDialog(parent)
{
	this->setTitle("Search");
	this->setSizePolicy(Policy_Preferred | Policy_Expanding);

	BHBoxLayout* upperLayout = new BHBoxLayout();
	{
		_searchOnlyButton = new BButton("Search");
		_findReplaceButton = new BButton("Replace");

		_modeButtonGroup = new BButtonGroup();
		_modeButtonGroup->addButton(_searchOnlyButton);
		_modeButtonGroup->addButton(_findReplaceButton);

		upperLayout->addWidget(_searchOnlyButton);
		upperLayout->addWidget(_findReplaceButton);
		upperLayout->addSpring();
	}

	BVBoxLayout* inputLayout = new BVBoxLayout("input");
	inputLayout->setMargin(Part_Left, 20);
	{
		_inputComboBox = new BComboBox();
		_inputComboBox->setEditable(true);
		_replaceComboBox = new BComboBox();
		_replaceComboBox->setEditable(true);
		_caseSensitiveCheckBox = new BCheckBox("Case sensitive");
		_fullWordCheckBox = new BCheckBox("Full words");
		_useRegularCheckBox = new BCheckBox("Use regular expression");

		inputLayout->addWidget(_inputComboBox);
		inputLayout->addWidget(_replaceComboBox);
		inputLayout->addWidget(_caseSensitiveCheckBox);
		inputLayout->addWidget(_fullWordCheckBox);
		inputLayout->addWidget(_useRegularCheckBox);
		inputLayout->setAlign(_caseSensitiveCheckBox, Align_Left);
		inputLayout->setAlign(_fullWordCheckBox, Align_Left);
		inputLayout->setAlign(_useRegularCheckBox, Align_Left);
	}

	BLabel* lookinLabel = new BLabel("search region");

	BVBoxLayout* lookinLayout = new BVBoxLayout("lookin");
	lookinLayout->setMargin(Part_Left, 20);
	lookinLayout->setWidthPolicy(Policy_Dynamic);
	{
		BButton* workspaceButton = new BRadioBox("Workspace");
		BButton* currentButton = new BRadioBox("Current text only");
		BButton* openedButton = new BRadioBox("All opened text");

		workspaceButton->setName("workspace");
		currentButton->setName("current");
		openedButton->setName("opened");

		_lookinButtonGroup = new BButtonGroup();
		_lookinButtonGroup->addButton(workspaceButton);
		_lookinButtonGroup->addButton(currentButton);
		_lookinButtonGroup->addButton(openedButton);

		lookinLayout->addWidget(workspaceButton);
		lookinLayout->addWidget(currentButton);
		lookinLayout->addWidget(openedButton);

		lookinLayout->setAlign(workspaceButton, Align_Left);
		lookinLayout->setAlign(currentButton, Align_Left);
		lookinLayout->setAlign(openedButton, Align_Left);

	}

	BVBoxLayout* vlayout = new BVBoxLayout("center", this);
	vlayout->addLayout(upperLayout);
	vlayout->addWidget(new BWidget("separator"));
	vlayout->addLayout(inputLayout);
	vlayout->addWidget(lookinLabel);
	vlayout->addLayout(lookinLayout);
	vlayout->addSpring();

	BHBoxLayout* bottomLayout = new BHBoxLayout(this, Part_Bottom);
	{
		_backwardButton = new BButton("Backward");
		_forwardButton = new BButton("Forward");
		_entireButton = new BButton("Entire");
		bottomLayout->addSpring()->setMinWidth(100);
		bottomLayout->addWidget(_backwardButton);
		bottomLayout->addWidget(_forwardButton);
		bottomLayout->addWidget(_entireButton);
	}

	connect(this, Signal_AssetChanged, &SearchDialog::slotAssetChanged);
	connect(this, Signal_PropertyChanged, &SearchDialog::slotPropertyChanged);

	connect(_modeButtonGroup, Signal_Checked, &SearchDialog::slotModeButtonChecked);
	connect(_inputComboBox, Signal_TextEdited, &SearchDialog::slotInputEditStarted);
	connect(_caseSensitiveCheckBox, Signal_Toggled, &SearchDialog::slotCheckBoxToggled);
	connect(_fullWordCheckBox, Signal_Toggled, &SearchDialog::slotCheckBoxToggled);
	connect(_useRegularCheckBox, Signal_Toggled, &SearchDialog::slotCheckBoxToggled);
	connect(_backwardButton, Signal_Clicked, &SearchDialog::slotBackwardButtonClicked);
	connect(_forwardButton, Signal_Clicked, &SearchDialog::slotForwardButtonClicked);
	connect(_entireButton, Signal_Clicked, &SearchDialog::slotEntireButtonClicked);

	_searchOnlyButton->setChecked(true);
	_lookinButtonGroup->button("workspace")->setChecked(true);
	_lookinButtonGroup->button("current")->setEnabled(false);
}
SearchDialog::~SearchDialog()
{

}

void SearchDialog::searchTreeItem(BTreeItem* item)
{
	const BString& src = _inputComboBox->text();
	BEntry* entry = item->asset();
	if (entry->attrib() & Attrib_Folder)
	{
		for (int i = 0; i < item->childCount(); i++)
		{
			searchTreeItem(item->child(i));
		}
		return;
	}

	BString text;
	if (item->property("docuemnt").empty())
	{
		if (!BFile::Load(entry->path(), text))
			return;
		item->setProperty("document", text);
	}
	else
	{
		text = (const BString&)item->property("document");
	}
	BTreeItemArray resultItems;
	int pos = 0;
	int lineNumber = 0;
	BString lineText;
	while (pos < text.size())
	{
		lineNumber++;
		pos = text.line(lineText, pos);
		if (lineText.empty())
			continue;
		auto it = lineText.find(src, lineText.begin());
		if (it.empty())
			continue;
		int index = it.count();
		BTreeItem* recordItem = new BTreeItem(lineText);
		recordItem->setProperty("line", lineNumber);
		recordItem->setProperty("index", index);
		recordItem->setAsset(item);
		resultItems.append(recordItem);
	}
	if (_findReplaceButton->checked())
	{
		const BString& dst = _replaceComboBox->text();
		int count = text.replace(src, dst);
		item->setProperty("document", text);
	}
	if (resultItems.size())
	{
		BTreeItem* resultItem = _resultTreeWidget->create(item->path());
		resultItem->setIcon(item->icon());
		for (int i = 0; i < resultItems.size(); i++)
		{
			resultItem->addChild(resultItems[i]);
		}
	}
}
void SearchDialog::searchText(BTextEdit* textEdit)
{
	const BString& src = _inputComboBox->text();
	BString text = textEdit->text();
	BTreeItemArray resultItems;
	int pos = 0;
	int lineNumber = 0;
	BString lineText;
	while (pos < text.size())
	{
		lineNumber++;
		pos = text.line(lineText, pos);
		if (lineText.empty())
			continue;
		auto it = lineText.find(src, lineText.begin());
		if (it.empty())
			continue;
		int index = it.count();
		BTreeItem* recordItem = new BTreeItem(lineText);
		recordItem->setProperty("line", lineNumber);
		recordItem->setProperty("index", index);
		resultItems.append(recordItem);
	}
	if (resultItems.size())
	{
		if (BScript* script = textEdit->asset())
		{
			BTreeItem* scriptItem = _resultTreeWidget->item(script->name());
			if (!scriptItem)
			{
				scriptItem = _resultTreeWidget->create(script->name());
				scriptItem->setAsset(script);
			}
			for (int i = 0; i < resultItems.size(); i++)
			{
				scriptItem->addChild(resultItems[i]);
			}
		}
	}
}

void SearchDialog::slotAssetChanged(BObject* object, const BValue& value)
{

}
void SearchDialog::slotPropertyChanged(BObject* object, const BValue& value)
{
	const BString& name = value;
	if (name == "workspace")
	{

	}
	if (name == "editspace")
	{
		BTabWidget* tabWidget = this->property(name);
		connect(tabWidget, Signal_CurrentChanged, &SearchDialog::slotTabWidgetCurrentChanged);
	}
	if (name == "results")
	{
		BTreeWidget* treeWidget = this->property(name);
		_resultTreeWidget = treeWidget;
	}
}
void SearchDialog::slotTabWidgetCurrentChanged(BObject* object, const BValue& value)
{
	BWidget* current = value;
	_lookinButtonGroup->button("current")->setEnabled(current);
	_forwardButton->setEnabled(current);
}

void SearchDialog::slotModeButtonChecked(BObject* object, const BValue& value)
{
	_replaceComboBox->setVisible(_findReplaceButton->checked());
}
void SearchDialog::slotInputEditStarted(BObject* object, const BValue& value)
{
	_inputComboBox->setCurrent(0);
}
void SearchDialog::slotCheckBoxToggled(BObject* object, const BValue& value)
{
	_memoryDocument.setAsset(true);
}
void SearchDialog::slotBackwardButtonClicked(BObject* object, const BValue& value)
{
	BTabWidget* tabWidget = this->property("editspace");
	BTextEdit* textEdit = dynamic_cast<BTextEdit*>(tabWidget->current());
	const BString& target = _inputComboBox->text();
	const BString& text = textEdit->text();
	int size = target.count();
	if (_position.valid())
		_position = text.rfind(target, _position - size);
	else
		_position = text.rfind(target, text.end());
	if (_position.valid())
	{
		int begin = _position.count();
		int end = begin + target.count();
		textEdit->input("select-range", BRange(begin, end));
		textEdit->scrollToRange(BRange(begin, end));
	}
	this->emit("search-backward");
}
void SearchDialog::slotForwardButtonClicked(BObject* object, const BValue& value)
{
	BTextEdit* textEdit = this->property("current-page");
	const BString& target = _inputComboBox->text();
	const BString& text = textEdit->text();
	int size = target.count();
	if (_position.valid())
		_position = text.find(target, _position + size);
	else
		_position = text.find(target, text.begin());
	if (_position.valid())
	{
		int begin = _position.count();
		int end = begin + target.count();
		textEdit->select(begin, end);
		textEdit->scrollToRange(BRange(begin, end));
	}
	this->emit("search-forward");
}
void SearchDialog::slotEntireButtonClicked(BObject* object, const BValue& value)
{
	_resultTreeWidget->clearItems();
	BButton* button = _lookinButtonGroup->checkedButton();
	if (button->name() == "workspace")
	{
		if (BTreeWidget* treeWidget = this->property("workspace"))
		{
			for (int i = 0; i < treeWidget->itemCount(); i++)
			{
				searchTreeItem(treeWidget->item(i));
			}
		}
	}
	else if (button->name() == "current")
	{
		if (BTextEdit* textEdit = dynamic_cast<BTextEdit*>(_editTabWidget->current()))
		{
			searchText(textEdit);
		}
	}
	else if (button->name() == "opened")
	{
		for (int i = 0; i < _editTabWidget->tabCount(); i++)
		{
			if (BTreeItem* item = _editTabWidget->tab(i)->asset())
			{
				searchTreeItem(item);
			}
		}
	}
	const BString& text = _inputComboBox->text();
	if (BComboItem* item = _inputComboBox->itemOfText(text))
	{
		_inputComboBox->moveItem(item, 0);
	}
	else
	{
		_inputComboBox->insertItem(0, text);
	}
	_memoryDocument.setAsset(true);
	this->close(true);
	this->emit("search-finished");
}

void SearchDialog::showEvent(const BEvent& event)
{
	BDialog::showEvent(event);
	if (_memoryDocument.nodeCount() == 0)
	{
		AutoWorkPath workpath(BApplication::appPath());
		if (const BXmlNode* root = _memoryDocument.load("temps/search.xml"))
		{
			if (const BXmlNode* child = root->child("case-sensitive"))
				_caseSensitiveCheckBox->setChecked(child->attrib("value") == "true");
			if (const BXmlNode* child = root->child("full-words"))
				_fullWordCheckBox->setChecked(child->attrib("value") == "true");
			if (const BXmlNode* child = root->child("use-regular"))
				_useRegularCheckBox->setChecked(child->attrib("value") == "true");
			if (const BXmlNode* child = root->child("look-in"))
			{
				const BString& name = child->attrib("name");
				if (BButton* button = _lookinButtonGroup->button(name))
					button->setChecked(true);
			}
			if (const BXmlNode* inputListNode = root->child("input-list"))
			{
				_inputComboBox->clearItems();
				BString text = inputListNode->text();
				text.trimmed();
				BStringArray words = text.split(';');
				for (int i = 0; i < words.size(); i++)
				{
					BString& word = words[i];
					word.trimmed();
					_inputComboBox->addItem(word);
				}
				_inputComboBox->setCurrentIndex(0);
			}
			if (const BXmlNode* replaceListNode = root->child("replace-list"))
			{
				_replaceComboBox->clearItems();
				const BString& text = replaceListNode->text();
				BStringArray words = text.split(';');
				for (int i = 0; i < words.size(); i++)
				{
					_replaceComboBox->addItem(words[i]);
				}
				_replaceComboBox->setCurrentIndex(0);
			}
		}
	}
	if (BTabWidget* tabWidget = this->property("edit-tabs"))
	{
		if (BTextEdit* textEdit = dynamic_cast<BTextEdit*>(tabWidget->current()))
		{
			BString text = textEdit->selectedText();
			if (text.size())
				_inputComboBox->setText(text);
		}
	}
}
void SearchDialog::hideEvent(const BEvent& event)
{
	BDialog::hideEvent(event);
	if (_memoryDocument.asset())
	{
		_memoryDocument.clearNodes();
		if (BXmlNode* root = _memoryDocument.addNode("root"))
		{
			root->addChild("case-sensitive")->setAttrib("value", _caseSensitiveCheckBox->checked() ? "true" : "false");
			root->addChild("full-words")->setAttrib("value", _fullWordCheckBox->checked() ? "true" : "false");
			root->addChild("use-regular")->setAttrib("value", _useRegularCheckBox->checked() ? "true" : "false");
			root->addChild("look-in")->setAttrib("name", _lookinButtonGroup->checkedButton()->name());
			if (BXmlNode* inputListNode = root->addChild("input-list"))
			{
				BString text;
				for (int i = 0; i < _inputComboBox->itemCount(); i++)
				{
					BComboItem* item = _inputComboBox->item(i);
					text << item->text() << ";\n";
				}
				inputListNode->setText(text);
			}
			if (BXmlNode* replaceListNode = root->addChild("replace-list"))
			{
				BString text;
				for (int i = 0; i < _replaceComboBox->itemCount(); i++)
				{
					BComboItem* item = _replaceComboBox->item(i);
					text << item->text() << ";\n";
				}
				replaceListNode->setText(text);
			}
		}
		AutoWorkPath workpath(BApplication::appPath());
		_memoryDocument.setFormat("<>\n</>\n");
		_memoryDocument.save("temps/search.xml");
		_memoryDocument.setAsset(false);
	}
}

void SearchDialog::paintEvent(const BEvent& event)
{
	BDialog::paintEvent(event);
}
